package reci.journal.utils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Random;

import javax.crypto.*;
import javax.crypto.spec.*;

/*
 * Copyright (C) 2014 ReciGames
 * See notice in reci.journal.Main.java.
 */

/**encrypts and decrypts*/
public class Crypter
{
	private static final int IV_LENGTH=16;
	
	/**
	 * Encrypts file.
	 * 
	 * @param filepath
	 * @param password
	 * @throws Exception
	 */
	public static void encrypt(String filepath, char[] password) throws Exception
	{
		String aesFile=filepath+".aes";
		
		BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(aesFile));
		
		//writes random iv to file
		byte[] iv=new byte[IV_LENGTH];
		new Random().nextBytes(iv);
		bos.write(iv);
		
		//
		// write encrypted file
		//
		Cipher cipher=getCipher(password,Cipher.ENCRYPT_MODE,iv);
		
		BufferedInputStream in=new BufferedInputStream(new FileInputStream(filepath));
		CipherOutputStream out=new CipherOutputStream(bos,cipher);
		
		ExtUtil.copyAndClose(in,out);
		
		//rename encrypted file to overwrite plaintext file
		new File(filepath).delete();
		
		if(!new File(aesFile).renameTo(new File(filepath)))
		{
			throw new IOException("Unable to rename decrypted file.");
		}
	}
	
	/**
	 * Decrypts file.
	 * 
	 * @param aesPath
	 * @param password
	 * @return path to decrypted file
	 * @throws IOException
	 */
	public static String decrypt(String aesPath, char[] password) throws Exception
	{
		String zipPath=aesPath+".zip";
		BufferedInputStream bis=null;
		CipherInputStream in=null;
		BufferedOutputStream out=null;
		
		try
		{
			bis=new BufferedInputStream(new FileInputStream(aesPath));
			
			//read iv
			byte[] iv=new byte[IV_LENGTH];
			bis.read(iv);
			
			Cipher cipher=getCipher(password,Cipher.DECRYPT_MODE,iv);
			
			in=new CipherInputStream(bis,cipher);
			out=new BufferedOutputStream(new FileOutputStream(zipPath));

			//write decrypted file
			ExtUtil.copyAndClose(in,out);
		}
		catch(Exception e)
		{
			throw new Exception("Unable to decrypt file.",e);
		}
		finally
		{
			ExtUtil.close(bis);
			ExtUtil.close(in);
			ExtUtil.close(out);
		}
			
		return zipPath;
	}
	
	/**
	 * @param password
	 * @param cipherMode
	 * @param iv
	 * @return cipher
	 * @throws GeneralSecurityException
	 */
	private static Cipher getCipher(char[] password, int cipherMode, byte[] iv) throws GeneralSecurityException
	{
		//convert password to byte[]
		CharBuffer charBuffer=CharBuffer.wrap(password.clone());
		ByteBuffer byteBuffer=Charset.forName("UTF-8").encode(charBuffer);
		byte[] passwordBytes=Arrays.copyOfRange(byteBuffer.array(),byteBuffer.position(),byteBuffer.limit());
		
		//digest password to key
		MessageDigest digest=MessageDigest.getInstance("SHA-256");
		byte[] key=Arrays.copyOfRange(digest.digest(passwordBytes),0,16);
		
		//clear sensitive data
		Arrays.fill(charBuffer.array(),(char)0);
		Arrays.fill(byteBuffer.array(),(byte)0);
		
		//get cipher
		Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
		final SecretKeySpec secretKey=new SecretKeySpec(key,"AES");
		cipher.init(cipherMode,secretKey,new IvParameterSpec(iv));
		return cipher;
	}
}
